package jp.co.nes.awssupport.report.utility;

import java.io.File;
import java.io.IOException;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.AwsCloudFrontHelper;
import jp.co.nes.awssupport.common.utility.AwsS3Helper;
import jp.co.nes.awssupport.common.utility.DateUtil;
import jp.co.nes.awssupport.common.utility.PropertiesUtil;
import jp.co.nes.awssupport.report.constant.ReportType;

/**
 * レポートヘルパーです。
 */
public class ReportHelper {

    /** allReportMapで使用するマップのキー */
    private enum MapKey {
        Csv, Excel, Pdf
    };

    /** レポートタイプ */
    private ReportType reportType;

    /** 処理対象年月 */
    private YearMonth targetYearMonth;

    /** レポートリストのマップ */
    private Map<MapKey, List<File>> allReportMap;

    /** オブジェクトキーリスト */
    private List<String> objectKeyList;

    /** 有効期限 */
    private Date expirationDate;

    /** レポートをs3へアップロードするかどうかのフラグ */
    private boolean isUpdateToS3 = false;

    /** 署名付きURLを取得するかどうかのフラグ */
    private boolean isGenerate = false;

    /**
     * レポートタイプ、処理対象年月を引数にもつコンストラクタです。<br>
     * 本コンストラクタでインスタンスを生成した場合、レポートをs3へアップロードするかどうかのフラグ、
     * 署名付きURLを取得するかどうかのフラグはtrueに設定されるので、s3へアップロードやs3や署名付きURLを取得に関する
     * 処理を実行すると、s3やCloudFrontにアクセスします。
     *
     * @param reportType レポートタイプ
     * @param targetYearMonth 処理対象年月
     */
    public ReportHelper(ReportType reportType, YearMonth targetYearMonth) {
        this(reportType, targetYearMonth, true, true);
    }

    /**
     * レポートタイプ、処理対象年月、レポートをs3へアップロードするかどうかのフラグ、署名付きURLを取得するかどうかのフラグを
     * 引数にもつコンストラクタです。
     *
     * @param reportType レポートタイプ
     * @param targetYearMonth 処理対象年月
     * @param isUpdateToS3 true: レポートをs3へアップロードします, false: レポートをs3へアップロードしません。
     * @param isGenerate true:  署名付きURLを取得します, false:  署名付きURLを取得しません。
     */
    public ReportHelper(ReportType reportType, YearMonth targetYearMonth, boolean isUpdateToS3, boolean isGenerate) {
        this.reportType = reportType;
        this.targetYearMonth = targetYearMonth;
        this.allReportMap = new HashMap<>();
        this.objectKeyList = new ArrayList<>();

        // 有効期間(月数)を取得する。
        String category = "Report";
        long diffMonth = AppInfoUtil.getLongValAndEmptyChk(category, "ReferenceUrlExpirationMonth");

        YearMonth expirationYearMonth = targetYearMonth.plusMonths(diffMonth);
        int year = expirationYearMonth.getYear();
        int month = expirationYearMonth.getMonthValue();
        int day = expirationYearMonth.lengthOfMonth();
        int hour = 23;
        int minute = 59;
        int second = 59;
        int nanoOfSecond = 999000000; // Date型の精度(ミリ秒)の最大値
        ZonedDateTime zdt = DateUtil.toZonedDateTime(year, month, day, hour, minute, second, nanoOfSecond);
        this.expirationDate = DateUtil.toDate(zdt);

        this.isUpdateToS3 = isUpdateToS3;
        this.isGenerate = isGenerate;
    }

    /**
     * レポート格納状況を返却します。
     *
     * @return true: レポートが格納されていません, false: レポートが格納されています。
     */
    public boolean isEmptyReport() {
        return allReportMap.isEmpty();
    }

    /**
     * オブジェクトキー格納状況を返却します。
     *
     * @return true: オブジェクトキーが格納されていません, false: オブジェクトキーが格納されています。
     */
    public boolean isEmptyObjectKey() {
        return objectKeyList.isEmpty();
    }

    /**
     * レポート(CSV)を追加します。
     *
     * @param csvReport レポート(CSV)
     */
    public void addCsvReport(File csvReport) {
        addReport(csvReport, MapKey.Csv);
    }

    /**
     * レポート(ExcelとPDF)を追加します。
     *
     * @param excelReport レポート(Excel)
     * @param pdfReport レポート(PDF)
     */
    public void addExcelPdfReport(File excelReport, File pdfReport) {
        addReport(excelReport, MapKey.Excel);
        addReport(pdfReport, MapKey.Pdf);
    }

    /**
     * レポートを追加します。
     *
     * @param report レポート
     * @param mapKey マップのキー
     */
    private void addReport(File report, MapKey mapKey) {
        String exceptionMessage = "ReportType or MapKey is wrong";
        if (reportType == ReportType.Csv) {
            if (mapKey != MapKey.Csv) {
                throw new IllegalArgumentException(exceptionMessage);
            }
        } else {
            if (mapKey != MapKey.Excel && mapKey != MapKey.Pdf) {
                throw new IllegalArgumentException(exceptionMessage);
            }
        }
        if (report == null) {
            throw new IllegalArgumentException("report is null");
        }

        List<File> list = allReportMap.get(mapKey);
        if (list == null) {
            list = new ArrayList<>();
            allReportMap.put(mapKey, list);
        }
        list.add(report);
    }

    /**
     * 本クラスに追加したレポートをs3へアップロードします。<br>
     * レポートが1つも追加されていない場合は、何もせず、処理を抜けます。
     *
     * @param bucketName s3にアップロードする際に使用するバケット名
     * @param folderName s3にアップロードする際に使用するフォルダ名(最後に/は不要)(%1$s:処理対象年月(uuuuMM))
     */
    public void uploadToS3(String bucketName, String folderName) {
        if (isEmptyReport()) {
            return;
        }

        String accessKey = PropertiesUtil.getProperty("ReportS3UploadAccessKey");
        String secretKey = PropertiesUtil.getProperty("ReportS3UploadSecretKey");

        AwsS3Helper helper = null;
        List<List<File>> allReportList = new ArrayList<>();
        allReportList.addAll(allReportMap.values());
        for (List<File> reportList : allReportList) {
            for (File report : reportList) {
                StringBuilder objectKeyBuilder = new StringBuilder();
                objectKeyBuilder.append(String.format(folderName, DateUtil.toString(targetYearMonth)));
                objectKeyBuilder.append("/");
                objectKeyBuilder.append(report.getName());
                String objectKey = objectKeyBuilder.toString();
                if (isUpdateToS3) {
                    if (helper == null) {
                        if (StringUtils.isEmpty(accessKey)) {
                            helper = new AwsS3Helper();
                        } else {
                            helper = new AwsS3Helper(accessKey, secretKey);
                        }
                    }
                    helper.putObject(bucketName, objectKey, report);
                }
                objectKeyList.add(objectKey);
            }
        }
    }

    /**
     * s3へアップロードしたオブジェクトキー毎に、署名付きURLを取得し、返却します。
     *
     * @return 署名付きURLを格納したマップ(キー：オブジェクトキー, 値：署名付きURL)
     * @throws InvalidKeySpecException 署名付きURLの取得に失敗した場合、スローします。
     * @throws IOException 署名付きURLの取得に失敗した場合、スローします。
     */
    public Map<String, String> generate() throws InvalidKeySpecException, IOException {
        Map<String, String> urlMap = new HashMap<String, String>();
        for (String objectKey : objectKeyList) {
            // 対象のオブジェクトキー毎に、署名付きURLを取得し、マップに格納する。
            String signedURL = null;
            if (isGenerate) {
                String category = "Report";
                String filename = AppInfoUtil.getValAndEmptyChk(category, "CloudFrontKeyPairPrivateKeyFileName");
                String id = AppInfoUtil.getValAndEmptyChk(category, "CloudFrontKeyPairId");
                String protocol = AppInfoUtil.getValAndEmptyChk(category, "CloudFrontProtocol");
                String domainName = AppInfoUtil.getValAndEmptyChk(category, "CloudFrontDomainName");
                String charSet = AppInfoUtil.getVal(category, "CloudFrontUrlEncodeCharset", "UTF-8");
                AwsCloudFrontHelper helper = new AwsCloudFrontHelper();
                signedURL = helper.generateSignedURL(protocol, domainName, filename, objectKey, id, expirationDate,
                        charSet);
            }
            urlMap.put(objectKey, signedURL);
        }
        return urlMap;
    }

    /**
     * ポータルサイトに反映する内容を作成し、返却します。
     *
     * @param dispReportName ポータルサイトに表示する内容(リンク名)
     * @param url 署名付きURL
     * @return ポータルサイトに反映する内容
     */
    public String createURLForPortalField(String dispReportName, String url) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        StringBuilder builder = new StringBuilder();
        builder.append("<a href='");
        builder.append(url);
        builder.append("' >");
        builder.append(String.format(dispReportName, DateUtil.toString(targetYearMonth), sdf.format(expirationDate)));
        builder.append("</a>");
        return builder.toString();
    }
}
